package gui.batches;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import java.util.TreeMap;

import core.Item;
import core.ItemFacade;
import core.Notification;
import core.NotificationType;
import core.Product;
import core.exception.ModelException;
import gui.common.*;
import gui.item.ItemData;
import gui.product.ProductData;

public abstract class ItemBatchController extends Controller implements Observer {

	protected List<Item> _allChangedItems;
	private ArrayList<ProductData> _addedProductData;
	private Map<Product,ProductData> _addedProducts;
	private IBatchView _theView;
	private CommandHistory _history;
	
	protected ItemBatchController(IView view) {
		super(view);
		_theView = (IBatchView)view;
		ItemFacade.instance().addObserver(this);
		
		_allChangedItems = new ArrayList<Item>();
		_addedProducts = new TreeMap<Product,ProductData>();
		_addedProductData = new ArrayList<ProductData>();		
		_history = new CommandHistory();
	}
	
	/**
	 * Return true if the redo button should be enabled
	 * @return true if the redo button should be enabled
	 */
	public boolean canRedo(){
		return _history.canRedo();
	}
	/**
	 * Return true if the undo button should be enabled
	 * @return true if the undo button should be enabled
	 */
	public boolean canUndo(){
		return _history.canUndo();
	}
	/**
	 * This method is called when the user clicks the "Redo" button
	 * in the add item batch view.
	 */
	public void redo() {
		try {
			_history.redo();
		} catch (ModelException e) {
			_theView.displayErrorMessage(e.getMessage());
		}
	}

	/**
	 * This method is called when the user clicks the "Undo" button
	 * in the add item batch view.
	 */
	public void undo() {
		try {
			_history.undo();
		} catch (ModelException e) {
			_theView.displayErrorMessage(e.getMessage());
		}
	}	
	
	

	/**
	 * This method is called when the selected product changes
	 * in the add item batch view.
	 */
	public void selectedProductChanged() {
		loadValues();
	}	
	
	/**
	 * Respond to a change in the model.
	 * @param arg0 The observable object that sent the notification
	 * @param arg1 A Notification Object describing the change.
	 */
	public void update(Observable arg0, Object arg1) {
		Notification n = (Notification)arg1;
		if(n.getType() == NotificationType.ItemsAdded ||
				n.getType() == NotificationType.ItemsRemoved){
			@SuppressWarnings("unchecked")
			List<Item> added = (List<Item>) n.getTag();
			onUsedItems(added);
		}
		else if (n.getType() == NotificationType.ItemAdded ||
				n.getType() == NotificationType.ItemRemoved ||
				n.getType() == NotificationType.ItemChanged){
			onUsedItem((Item)n.getTag());
		}

		loadValues();
		
	}

	private void removeProduct(Product product) {
		if (_addedProducts.containsKey(product)){
			ProductData data = _addedProducts.get(product);
			_addedProductData.remove(data);
			_addedProducts.remove(product);	
			
			if (_theView.getSelectedProduct() == data)
				_theView.selectProduct(null);
		}
	}	
	
	/**
	 * Register the command on the undo/redo stack and execute it.
	 * @param c The command
	 */
	public void doCommand(Command c){
		try {
			c.execute();
			_history.addCommandToHistory(c);
		} catch (ModelException e) {
			e.printStackTrace();
			_theView.displayErrorMessage(e.getMessage());
		}
	}	
	

	/**
	 * Load all the values into the view
	 */
	protected void loadValues() {
		// load in all the products
		ProductData selected = _theView.getSelectedProduct();
		
		// update the product table
		_theView.setProducts(_addedProductData.toArray(new ProductData[0]));
		
		// don't lose selection
		_theView.selectProduct(selected);
		
		// Load in all the items
		if (selected != null){
			Iterator<Item> items = _allChangedItems.iterator();
			
			ArrayList<ItemData> itemsForProduct = new ArrayList<ItemData>();
			while(items.hasNext()){
				Item i = items.next();
				if(i.getProduct().equals(selected.getProduct()))
					itemsForProduct.add(new ItemData(i));		
			}
			_theView.setItems(itemsForProduct.toArray(new ItemData[0]));		
		}
		enableComponents();
	}

	private void onUsedProduct(Product product) {
		if (!_addedProducts.containsKey(product)){
			ProductData data = new ProductData(product);
			data.setCount("0");
			_addedProducts.put(product, data);
			_addedProductData.add(data);
		}
	}

	/**
	 * This method is currently used for child classes to notify this class
	 * when something happens.
	 * @param item
	 */
	protected void onUsedItem(Item item) {
		ArrayList<Item> items = new ArrayList<Item>();
		items.add(item);
		onUsedItems(items);	
	}

	private void onUsedItems(List<Item> added) {
		if (added.isEmpty())
			return;
		
		onUsedProduct(added.get(0).getProduct());
		
		int countChange = 0;
		if (_allChangedItems.containsAll(added)){
			_allChangedItems.removeAll(added);
			countChange = -added.size();
		}
		else{
			_allChangedItems.addAll(added);
			countChange = added.size();
		}
		
		int curCount = Integer.parseInt(_addedProducts.get(added.get(0).getProduct()).getCount());
		curCount += countChange;
		if (curCount != 0)
			_addedProducts.get(added.get(0).getProduct()).setCount(String.valueOf(curCount));
		else
			removeProduct(added.get(0).getProduct());
			
		
		loadValues();			
	}
	
	protected abstract void enableComponents();
	
	protected void onDone(){
		ItemFacade.instance().deleteObserver(this);
	}


}
